#pragma once

#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/noncopyable.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boostext/typedefs.hpp>

#include "exports.hpp"
#include "progress_listener.hpp"
#include "future.hpp"




namespace services
{
	/**
	 * A task binds the execution of an arbitrary functor to a future.
	 * It is used to execute a factory and to store the result / exception
	 * in the future.
	 */
	class SERVICES_API task
		: public boost::noncopyable
	{
	public:

		typedef boost::shared_ptr<task>            ptr;
		typedef boost::shared_ptr<const task>      const_ptr;
		typedef boost::function<void()>            functor;

	public:

		/**
		 * Creates a new task that takes care of calling the given functor, eventually
		 * storing the returned value inside the given future object.
		 */
		template <class F, class R>
		task(F fn, future<R> ft)
			: m_dedicated(false)
			, m_looped(false)
		{
			// We create a custom functor that does all the work
			// All we need to do is simply invoke the functor in
			// operator().
			// This way, task musn't need to be a template class
			m_fn = [=]() mutable
			{
				// We try to invoke the functor and store the return value inside
				// the future object
				try
				{
					ft.set_value(fn(m_prog));
				}
				catch(std::exception&)
				{
					// It is very possible that we cannot retrieve the exception through current_exception
					// If that happens, we create an instance of std::bad_exception and put it inside
					// the future object, so that it knows that something bad has happened (so it doesn't
					// wait forever...)
					std::exception_ptr except = std::current_exception();
					if(except == nullptr) //< Funfact #1: std::exception_ptr doesn't provide operator !=
						ft.set_exception(std::copy_exception(std::bad_exception()));
					else
						ft.set_exception(except);
				}
				catch(...)
				{
					// If the type of exception thrown is not even a std::exception, then we also
					// store a std::bad_exception in the future
					ft.set_exception(std::copy_exception(std::bad_exception()));
				}
			};
		}

		/**
		 * Creates a new task for the given functor and future object.
		 * This function is a specialization for a functor that doesn't return
		 * anything.
		 */
		template <typename F>
		task(F fn, future<void> ft)
			: m_dedicated(false)
			, m_looped(false)
		{
			// We create a custom functor that does all the work
			// All we need to do is simply invoke the functor in
			// operator().
			// This way, task musn't need to be a template class
			m_fn = [=]() mutable
			{
				// We try to invoke the functor and store the return value inside
				// the future object
				try
				{
					fn(m_prog);
					ft.set_value();
				}
				catch(std::exception&)
				{
					// It is very possible that we cannot retrieve the exception through current_exception
					// If that happens, we create an instance of std::bad_exception and put it inside
					// the future object, so that it knows that something bad has happened (so it doesn't
					// wait forever...)
					std::exception_ptr except = std::current_exception();
					if(except == nullptr) //< Funfact #1: std::exception_ptr doesn't provide operator !=
						ft.set_exception(std::copy_exception(std::bad_exception()));
					else
						ft.set_exception(except);
				}
				catch(...)
				{
					// If the type of exception thrown is not even a std::exception, then we also
					// store a std::bad_exception in the future
					ft.set_exception(std::copy_exception(std::bad_exception()));
				}
			};
		}



		/**
		 * Sets this task to be a dedicated one.
		 * Usually, an executor can use this as a hint to allocate more resources
		 * to this task, than to others.
		 *
		 * This is not part of the proposed task concept.
		 */
		void setDedicated(bool dedicated);

		/**
		 * Tests if this task is a dedicated one or not.
		 *
		 * This is not part of the proposed task concept.
		 */
		bool dedicated() const;

		/**
		 * Sets this task to be a looped one.
		 * Usually, an executor calls operator() on the task and then drops it, however
		 * some implementations can use this hint to call operatio() in a loop, without
		 * actually removing the task (and thus avoiding the overhead involved in removing
		 * & inserting the task endlessly).
		 *
		 * This is not part of the proposed task concept.
		 */
		void setLooped(bool looped);

		/**
		 * Tests if this task is a looped one or not.
		 *
		 * This is not part of the proposed task concept.
		 */
		bool looped() const;



		/**
		 * Cancels the task.
		 *
		 * If the functor accepted a progress_listener then this may
		 * cancel the task during execution. Otherwise this has no effect.
		 */
		void cancel();

		/**
		 * Tests if the task has been cancelled or not.
		 */
		bool cancelled() const;

		/**
		 * @returns the current progress of the task
		 */
		int progress() const;

		/**
		 * Executes the task.
		 */
		void operator()();

	private:

		volatile bool       m_dedicated;
		volatile bool       m_looped;
		functor             m_fn;
		progress_listener   m_prog;
	};
	///////////////////////////////////////////////////////////////////////////////////////////////
}
///////////////////////////////////////////////////////////////////////////////////////////////////
